package org.promote.hotspot.client.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.java.Log;
import org.promote.hotspot.client.core.server.ServerInfoHolder;
import org.promote.hotspot.common.coder.MsgDecoder;
import org.promote.hotspot.common.coder.MsgEncoder;
import org.promote.hotspot.common.model.HotSpotMsg;
import org.promote.hotspot.common.tool.Constant;

import java.util.List;

/**
 * @author enping.jep
 * @date 2023/11/15 11:39
 **/
@Log
public class NettyClient {
    private static final NettyClient nettyClient = new NettyClient();

    private final Bootstrap bootstrap;

    public static NettyClient getInstance() {
        return nettyClient;
    }

    private NettyClient() {
        bootstrap = initBootstrap();
    }

    private Bootstrap initBootstrap() {
        //只有2个工作线程，没有单独设置主线程
        EventLoopGroup group = new NioEventLoopGroup(2);

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group).channel(NioSocketChannel.class)
                //长连接
                .option(ChannelOption.SO_KEEPALIVE, true)
                // TCP_NODELAY 参数被设置为 true 时，表示禁用Nagle算法(为了尽可能发送大块数据，避免网络中充斥着许多小数据块)，数据会立即发送，而不会等待缓冲区填满或者延迟定时器到期，
                //  这对于需要低延迟的应用程序非常重要，例如实时通信或者交互式应用程序
                .option(ChannelOption.TCP_NODELAY, true)
                //入站处理顺序 [ Network Layer ] -- raw bytes --> [ DelimiterBasedFrameDecoder ] -- frames --> [ MsgDecoder ] -- messages --> [ NettyClientHandler ] -- business logic
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        // netty的分隔符”$( )$”，类似TCP报文分段的标准，方便拆包
                        ByteBuf delimiter = Unpooled.copiedBuffer(Constant.DELIMITER.getBytes());
                        ch.pipeline()
                                //用来解决以特殊符号作为消息结束符的粘包问题 当到达长度后仍然没有查找到分隔符，就抛TooLongFrameException
                                .addLast(new DelimiterBasedFrameDecoder(Constant.MAX_LENGTH, delimiter))//ChannelInboundHandler
                                .addLast(new MsgDecoder())//ChannelInboundHandler
                                .addLast(new MsgEncoder()) //ChannelOutboundHandler
                                // 如果30秒没有发送消息，则发送心跳包探活，netty中实现心跳机制的关键是 IdleStateHandler（空闲状态处理器）
                                // 作用跟名字一样，是用来监测连接的空闲情况。然后我们就可以根据心跳情况，来实现具体的处理逻辑，比如说断开连接、重新连接等等。
                                // 当IdleStateEvent 触发后 , 就会传递给管道 的下一个handler去处理，通过调用(触发)下一个handler 的 userEventTiggered ,
                                // 在该方法中去处理 IdleStateEvent(读空闲，写空闲，读写空闲)
                                // 参数设置为0，表示不监控
                                // IdleStateHandler虽然在链中，但它的主要作用是监控空闲状态，并不直接参与数据的解码或业务逻辑处理
                                .addLast(new IdleStateHandler(0, 0, 30))
                                .addLast(new NettyClientHandler());//ChannelInboundHandler
                    }
                });
        return bootstrap;
    }

    /**
     * 客户端连接服务端
     *
     * @param addresses 连接地址信息
     */
    public synchronized void connect(List<String> addresses) {
        for (String address : addresses) {
            if (ServerInfoHolder.hasConnected(address)) {
                continue;
            }
            //10.108.128.166:11111
            String[] ss = address.split(":");
            try {
                //调用connect方法连接到远程服务端
                ChannelFuture channelFuture = bootstrap.connect(ss[0], Integer.parseInt(ss[1])).sync();
                Channel channel = channelFuture.channel();
                ServerInfoHolder.put(address, channel);
            } catch (Exception e) {
                log.info("----该server连不上----" + address);
                ServerInfoHolder.put(address, null);
            }
        }
    }


}
